#include "U2GLESRenderSystem.h"

#include "U2LogManager.h"
#include "U2GLESContext.h"
#include "U2RenderWindow.h"
#include "U2GLESSupport.h"
#include "U2GLESUtil.h"
#include "U2HardwareBufferManager.h"
#include "U2GLESHardwareBufferManager.h"
#include "U2GLESDefaultHardwareBufferManager.h"
#include "U2GLESHardwareVertexBuffer.h"
#include "U2GLESHardwareIndexBuffer.h"
#include "U2GLESTextureManager.h"
#include "U2GLESRenderTexture.h"
#include "U2Viewport.h"
#include "U2GLESDepthBuffer.h"
#include "U2GLESFrameBufferObject.h"
#include "U2GLESFBORenderTexture.h"



// Convenience macro from ARB_vertex_buffer_object spec
#define VBO_BUFFER_OFFSET(i)            ((char *)NULL + (i))
// Copy this definition from desktop GL.  Used for polygon modes.
#define GL_FILL                         0x1B02



U2EG_NAMESPACE_USING



GLESRenderSystem::GLESRenderSystem() 
: mGLSupport(NULL)
, mMainContext(NULL)
, mCurrentContext(NULL)
, mDepthWrite(true)
, mStencilMask(0xFFFFFFFF)
, mGLInitialised(false)
, mHardwareBufferManager(0)
, mRTTManager(0)
, mActiveTextureUnit(0)
, mTextureMipmapCount(0)
, mDiscardBuffers(0)
{
    // Get function pointers on platforms that doesn't have prototypes
#ifndef GL_GLEXT_PROTOTYPES
    ::glIsRenderbufferOES = (PFNGLISRENDERBUFFEROESPROC)eglGetProcAddress("glIsRenderbufferOES");
    ::glBindRenderbufferOES = (PFNGLBINDRENDERBUFFEROESPROC)eglGetProcAddress("glBindRenderbufferOES");
    ::glDeleteRenderbuffersOES = (PFNGLDELETERENDERBUFFERSOESPROC)eglGetProcAddress("glDeleteRenderbuffersOES");
    ::glGenRenderbuffersOES = (PFNGLGENRENDERBUFFERSOESPROC)eglGetProcAddress("glGenRenderbuffersOES");
    ::glRenderbufferStorageOES = (PFNGLRENDERBUFFERSTORAGEOESPROC)eglGetProcAddress("glRenderbufferStorageOES");
    ::glGetRenderbufferParameterivOES = (PFNGLGETRENDERBUFFERPARAMETERIVOESPROC)eglGetProcAddress("glGetRenderbufferParameterivOES");
    ::glIsFramebufferOES = (PFNGLISFRAMEBUFFEROESPROC)eglGetProcAddress("glIsFramebufferOES");
    ::glBindFramebufferOES = (PFNGLBINDFRAMEBUFFEROESPROC)eglGetProcAddress("glBindFramebufferOES");
    ::glDeleteFramebuffersOES = (PFNGLDELETEFRAMEBUFFERSOESPROC)eglGetProcAddress("glDeleteFramebuffersOES");
    ::glGenFramebuffersOES = (PFNGLGENFRAMEBUFFERSOESPROC)eglGetProcAddress("glGenFramebuffersOES");
    ::glCheckFramebufferStatusOES = (PFNGLCHECKFRAMEBUFFERSTATUSOESPROC)eglGetProcAddress("glCheckFramebufferStatusOES");
    ::glFramebufferRenderbufferOES = (PFNGLFRAMEBUFFERRENDERBUFFEROESPROC)eglGetProcAddress("glFramebufferRenderbufferOES");
    ::glFramebufferTexture2DOES = (PFNGLFRAMEBUFFERTEXTURE2DOESPROC)eglGetProcAddress("glFramebufferTexture2DOES");
    ::glGetFramebufferAttachmentParameterivOES = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVOESPROC)eglGetProcAddress("glGetFramebufferAttachmentParameterivOES");
    ::glGenerateMipmapOES = (PFNGLGENERATEMIPMAPOESPROC)eglGetProcAddress("glGenerateMipmapOES");
    ::glBlendEquationOES = (PFNGLBLENDEQUATIONOESPROC)eglGetProcAddress("glBlendEquationOES");
    ::glBlendFuncSeparateOES = (PFNGLBLENDFUNCSEPARATEOESPROC)eglGetProcAddress("glBlendFuncSeparateOES");
    ::glBlendEquationSeparateOES = (PFNGLBLENDEQUATIONSEPARATEOESPROC)eglGetProcAddress("glBlendEquationSeparateOES");
    ::glMapBufferOES = (PFNGLMAPBUFFEROESPROC)eglGetProcAddress("glMapBufferOES");
    ::glUnmapBufferOES = (PFNGLUNMAPBUFFEROESPROC)eglGetProcAddress("glUnmapBufferOES");
#endif
    GL_CHECK_ERROR;

    U2LogManager::getSingleton().logMessage(getName() + " created.");

    mGLSupport = getGLSupport();
    mGLSupport->addConfig();

    mColourWrite[0] = mColourWrite[1] = mColourWrite[2] = mColourWrite[3] = true;

    for (size_t i = 0; i < U2_MAX_TEXTURE_LAYERS; i++)
    {
        // Dummy value
        mTextureCoordIndex[i] = 99;
    }

    mPolygonMode = GL_FILL;
    mMinFilter = FO_LINEAR;
    mMipFilter = FO_POINT;
}

GLESRenderSystem::~GLESRenderSystem()
{
    shutdown();

    U2_DELETE mGLSupport;
}

void GLESRenderSystem::shutdown(void)
{
    U2RenderSystem::shutdown();

    U2_DELETE mHardwareBufferManager;
    mHardwareBufferManager = 0;
    
    delete mRTTManager;
    mRTTManager = 0;
    
    mGLSupport->stop();
    
    U2_DELETE mTextureManager;
    mTextureManager = 0;
    
    mGLInitialised = 0;
}

const U2String& GLESRenderSystem::getName(void) const
{
    static U2String strName("OpenGL ES 1.x Rendering Subsystem");
    return strName;
}

U2ConfigOptionMap& GLESRenderSystem::getConfigOptions(void)
{
    return mGLSupport->getConfigOptions();
}

void GLESRenderSystem::setConfigOption(const U2String &name, const U2String &value)
{
    mGLSupport->setConfigOption(name, value);
}

U2String GLESRenderSystem::validateConfigOptions(void)
{
    // XXX Return an error string if something is invalid
    return mGLSupport->validateConfig();
}

void GLESRenderSystem::initialiseContext(U2RenderWindow* primary)
{
    // Set main and current context
    mMainContext = 0;
    primary->getCustomAttribute("GLCONTEXT", &mMainContext);
    mCurrentContext = mMainContext;

    // Set primary context as active
    if (mCurrentContext)
        mCurrentContext->setCurrent();

    // Setup GLSupport
    mGLSupport->initialiseExtensions();

    U2LogManager::getSingleton().logMessage("**************************************");
    U2LogManager::getSingleton().logMessage("*** OpenGL ES 1.x Renderer Started ***");
    U2LogManager::getSingleton().logMessage("**************************************");
}

void GLESRenderSystem::_unregisterContext(U2GLESContext *context)
{
    if (mCurrentContext == context)
    {
        // Change the context to something else so that a valid context
        // remains active. When this is the main context being unregistered,
        // we set the main context to 0.
        if (mCurrentContext != mMainContext)
        {
            _switchContext(mMainContext);
        }
        else
        {
            /// No contexts remain
            mCurrentContext->endCurrent();
            mCurrentContext = 0;
            mMainContext = 0;
        }
    }
}

void GLESRenderSystem::_switchContext(U2GLESContext *context)
{
    /**********************@@@@@@@@@@@@@@@@@@@@@
    // Disable textures
    _disableTextureUnitsFrom(0);
    */

    // It's ready for switching
    if(mCurrentContext)
        mCurrentContext->endCurrent();
    mCurrentContext = context;
    mCurrentContext->setCurrent();

    // Check if the context has already done one-time initialisation
    if (!mCurrentContext->getInitialized())
    {
        _oneTimeContextInitialization();
        mCurrentContext->setInitialized();
    }

    // Must reset depth/colour write mask to according with user desired, otherwise,
    // clearFrameBuffer would be wrong because the value we are recorded may be
    // difference with the really state stored in GL context.
    glDepthMask(mDepthWrite);
    glColorMask(mColourWrite[0], mColourWrite[1], mColourWrite[2], mColourWrite[3]);
    glStencilMask(mStencilMask);
}

void GLESRenderSystem::_oneTimeContextInitialization()
{
    glDisable(GL_DITHER);
    GL_CHECK_ERROR;

    int fsaa_active = false;
    glGetIntegerv(GL_SAMPLE_BUFFERS,(GLint*)&fsaa_active);
    GL_CHECK_ERROR;
    if (fsaa_active)
    {
        glEnable(GL_MULTISAMPLE);
        GL_CHECK_ERROR;
        U2LogManager::getSingleton().logMessage("Using FSAA OpenGL ES.");
    }
}

U2RenderSystemCapabilities* GLESRenderSystem::createRenderSystemCapabilities() const
{
    U2RenderSystemCapabilities* rsc = U2_NEW U2RenderSystemCapabilities();

    rsc->setCategoryRelevant(CAPS_CATEGORY_GL, true);
    rsc->setDriverVersion(mDriverVersion);

    const char* deviceName = (const char*)glGetString(GL_RENDERER);
	const char* vendorName = (const char*)glGetString(GL_VENDOR);        
    if (deviceName)
    {
        rsc->setDeviceName(deviceName);
    }

    rsc->setRenderSystemName(getName());

	// Determine vendor
	if (strstr(vendorName, "Imagination Technologies"))
		rsc->setVendor(GPU_IMAGINATION_TECHNOLOGIES);
	else if (strstr(vendorName, "Apple Computer, Inc."))
		rsc->setVendor(GPU_APPLE);  // iPhone Simulator
	else if (strstr(vendorName, "NVIDIA"))
		rsc->setVendor(GPU_NVIDIA);
	else if (strstr(vendorName, "Nokia"))
		rsc->setVendor(GPU_NOKIA);
    else
        rsc->setVendor(GPU_UNKNOWN);

    // GL ES 1.x is fixed function only
    rsc->setCapability(RSC_FIXED_FUNCTION);

    // Multitexturing support and set number of texture units
    GLint units;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &units);
    rsc->setNumTextureUnits(units);

    // Check for hardware stencil support and set bit depth
    GLint stencil;
    glGetIntegerv(GL_STENCIL_BITS, &stencil);
    GL_CHECK_ERROR;

    if(stencil)
    {
        rsc->setCapability(RSC_HWSTENCIL);
        rsc->setStencilBufferBitDepth(stencil);
    }

    // Scissor test is standard
    rsc->setCapability(RSC_SCISSOR_TEST);

    // Vertex Buffer Objects are always supported by OpenGL ES
    rsc->setCapability(RSC_VBO);

    // OpenGL ES - Check for these extensions too
    // For 1.1, http://www.khronos.org/registry/gles/api/1.1/glext.h

    if (mGLSupport->checkExtension("GL_IMG_texture_compression_pvrtc") ||
        mGLSupport->checkExtension("GL_AMD_compressed_3DC_texture") ||
        mGLSupport->checkExtension("GL_AMD_compressed_ATC_texture") ||
        mGLSupport->checkExtension("GL_OES_compressed_ETC1_RGB8_texture") ||
        mGLSupport->checkExtension("GL_OES_compressed_paletted_texture"))
    {
        // TODO: Add support for compression types other than pvrtc
        rsc->setCapability(RSC_TEXTURE_COMPRESSION);

        if(mGLSupport->checkExtension("GL_IMG_texture_compression_pvrtc"))
            rsc->setCapability(RSC_TEXTURE_COMPRESSION_PVRTC);
    }

    if (mGLSupport->checkExtension("GL_EXT_texture_filter_anisotropic"))
        rsc->setCapability(RSC_ANISOTROPY);

    // FIXME: DJR - causes GL errors on 3GS
//        if (mGLSupport->checkExtension("GL_APPLE_texture_2D_limited_npot"))
//            rsc->setCapability(RSC_NON_POWER_OF_2_TEXTURES);

    if (mGLSupport->checkExtension("GL_OES_framebuffer_object")) {
        rsc->setCapability(RSC_FBO);
        rsc->setCapability(RSC_HWRENDER_TO_TEXTURE);
    } else {
        rsc->setCapability(RSC_PBUFFER);
        rsc->setCapability(RSC_HWRENDER_TO_TEXTURE);
    }

    // Cube map
    if (mGLSupport->checkExtension("GL_OES_texture_cube_map"))
        rsc->setCapability(RSC_CUBEMAPPING);

    if (mGLSupport->checkExtension("GL_OES_stencil_wrap"))
        rsc->setCapability(RSC_STENCIL_WRAP);

    if (mGLSupport->checkExtension("GL_OES_blend_subtract"))
        rsc->setCapability(RSC_ADVANCED_BLEND_OPERATIONS);

//        if (mGLSupport->checkExtension("GL_IMG_user_clip_plane"))
        rsc->setCapability(RSC_USER_CLIP_PLANES);

    if (mGLSupport->checkExtension("GL_OES_texture3D"))
        rsc->setCapability(RSC_TEXTURE_3D);

    // GL always shares vertex and fragment texture units (for now?)
    rsc->setVertexTextureUnitsShared(true);

    // Hardware support mipmapping
    rsc->setCapability(RSC_AUTOMIPMAP);

    if (mGLSupport->checkExtension("GL_EXT_texture_lod_bias"))
        rsc->setCapability(RSC_MIPMAP_LOD_BIAS);

    // Blending support
    rsc->setCapability(RSC_BLENDING);

    // DOT3 support is standard
    rsc->setCapability(RSC_DOT3);
    
    // Point size
    float ps = 0;
    glGetFloatv(GL_POINT_SIZE_MAX, &ps);
    GL_CHECK_ERROR;
    rsc->setMaxPointSize(ps);

    // Point sprites
    if (mGLSupport->checkExtension("GL_OES_point_sprite"))
        rsc->setCapability(RSC_POINT_SPRITES);
    rsc->setCapability(RSC_POINT_EXTENDED_PARAMETERS);

    // UBYTE4 always supported
    rsc->setCapability(RSC_VERTEX_FORMAT_UBYTE4);

    // Infinite far plane always supported
    rsc->setCapability(RSC_INFINITE_FAR_PLANE);

    // Alpha to coverage always 'supported' when MSAA is available
    // although card may ignore it if it doesn't specifically support A2C
    rsc->setCapability(RSC_ALPHA_TO_COVERAGE);
    
    return rsc;
}

U2RenderWindow* GLESRenderSystem::_createRenderWindow(const U2String &name, unsigned int width, unsigned int height,
                                                    bool fullScreen, const NameValuePairList *miscParams)
{
    if (mRenderTargets.find(name) != mRenderTargets.end())
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
            "NativeWindowType with name '" + name + "' already exists",
            "GLESRenderSystem::_createRenderWindow");
    }

    // U2Log a message
    U2StringStream ss;
    ss << "GLESRenderSystem::_createRenderWindow \"" << name << "\", " <<
        width << "x" << height << " ";
    if (fullScreen)
        ss << "fullscreen ";
    else
        ss << "windowed ";

    if (miscParams)
    {
        ss << " miscParams: ";
        NameValuePairList::const_iterator it;
        for (it = miscParams->begin(); it != miscParams->end(); ++it)
        {
            ss << it->first << "=" << it->second << " ";
        }

        U2LogManager::getSingleton().logMessage(ss.str());
    }

    // Create the window
    U2RenderWindow* win = mGLSupport->newWindow(name, width, height, fullScreen, miscParams);
    attachRenderTarget((U2RenderTarget&) *win);

    if (!mGLInitialised)
    {
        initialiseContext(win);

        U2StringVector tokens = U2StringUtil::split(mGLSupport->getGLVersion(), ".");
        if (!tokens.empty())
        {
            mDriverVersion.major = U2StringConverter::parseInt(tokens[0]);
            if (tokens.size() > 1)
                mDriverVersion.minor = U2StringConverter::parseInt(tokens[1]);
            if (tokens.size() > 2)
                mDriverVersion.release = U2StringConverter::parseInt(tokens[2]);
        }
        mDriverVersion.build = 0;
        // Initialise GL after the first window has been created
        // TODO: fire this from emulation options, and don't duplicate u2real and Current capabilities
        mRealCapabilities = createRenderSystemCapabilities();

        // use real capabilities if custom capabilities are not available
        if (!mUseCustomCapabilities)
            mCurrentCapabilities = mRealCapabilities;

        fireEvent("RenderSystemCapabilitiesCreated");

        initialiseFromRenderSystemCapabilities(mCurrentCapabilities, (U2RenderTarget *) win);

        // Initialise the main context
        _oneTimeContextInitialization();
        if (mCurrentContext)
            mCurrentContext->setInitialized();
    }

    /**********************@@@@@@@@@@@@@@@@@@@@@
    if( win->getDepthBufferPool() != U2DepthBuffer::POOL_NO_DEPTH )
    {
        //Unlike D3D9, OGL doesn't allow sharing the main depth buffer, so keep them separate.
        //Only Copy does, but Copy means only one depth buffer...
        U2GLESDepthBuffer *depthBuffer = U2_NEW U2GLESDepthBuffer( U2DepthBuffer::POOL_DEFAULT, this,
            mCurrentContext, 0, 0,
            win->getWidth(), win->getHeight(),
            win->getFSAA(), 0, true );

        mDepthBufferPool[depthBuffer->getPoolId()].push_back( depthBuffer );

        win->attachDepthBuffer( depthBuffer );
    }
    */

    return win;
}

void GLESRenderSystem::initialiseFromRenderSystemCapabilities(U2RenderSystemCapabilities* caps, U2RenderTarget* primary)
{
    if(caps->getRenderSystemName() != getName())
    {
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
            "Trying to initialize GLESRenderSystem from U2RenderSystemCapabilities that do not support OpenGL ES",
            "GLESRenderSystem::initialiseFromRenderSystemCapabilities");
    }

    /**********************@@@@@@@@@@@@@@@@@@@@@
    mGpuProgramManager = U2_NEW GLESGpuProgramManager();
    */
    // Set texture the number of texture units
    mFixedFunctionTextureUnits = caps->getNumTextureUnits();
    
    if(caps->hasCapability(RSC_VBO))
    {
        mHardwareBufferManager = U2_NEW U2GLESHardwareBufferManager;
    }
    else
    {
        mHardwareBufferManager = U2_NEW U2GLESDefaultHardwareBufferManager;
    }

    // Check for framebuffer object extension
    if(caps->hasCapability(RSC_FBO))
    {
        if(caps->hasCapability(RSC_HWRENDER_TO_TEXTURE))
        {
            // Create FBO manager
            U2LogManager::getSingleton().logMessage("GLES: Using GL_OES_framebuffer_object for rendering to textures (best)");
            mRTTManager = new GLESFBOManager();
            caps->setCapability(RSC_RTT_SEPARATE_DEPTHBUFFER);
        }
    }
    else
    {
        /**********************@@@@@@@@@@@@@@@@@@@@@
        // Check GLSupport for PBuffer support
        if(caps->hasCapability(RSC_PBUFFER))
        {
            if(caps->hasCapability(RSC_HWRENDER_TO_TEXTURE))
            {
                // Use PBuffers
                mRTTManager = U2_NEW_FIX_FOR_WIN32 GLESPBRTTManager(mGLSupport, primary);
                U2LogManager::getSingleton().logMessage("GLES: Using PBuffers for rendering to textures");
            }
        }

        // Downgrade number of simultaneous targets
        caps->setNumMultiRenderTargets(1);
        */
    }
    

    U2Log* defaultLog = U2LogManager::getSingleton().getDefaultLog();
    if (defaultLog)
    {
        caps->log(defaultLog);
    }

    mTextureManager = U2_NEW U2GLESTextureManager(*mGLSupport);
    
    GL_CHECK_ERROR;
    mGLInitialised = true;
}

void GLESRenderSystem::_setViewport(U2Viewport *vp)
{
    // Check if viewport is different
    if (!vp)
    {
        mActiveViewport = NULL;
        _setRenderTarget(NULL);
    }
    else if (vp != mActiveViewport || vp->_isUpdated())
    {
        U2RenderTarget* target;

        target = vp->getTarget();
        _setRenderTarget(target);
        mActiveViewport = vp;

        GLsizei x, y, w, h;

        // Calculate the "lower-left" corner of the viewport
        w = vp->getActualWidth();
        h = vp->getActualHeight();
        x = vp->getActualLeft();
        y = vp->getActualTop();

        if (!target->requiresTextureFlipping())
        {
            // Convert "upper-left" corner to "lower-left"
            y = target->getHeight() - h - y;
        }

#if U2_NO_VIEWPORT_ORIENTATIONMODE == 0
        U2ConfigOptionMap::const_iterator opt;
        U2ConfigOptionMap::const_iterator end = mGLSupport->getConfigOptions().end();

        if ((opt = mGLSupport->getConfigOptions().find("Orientation")) != end)
        {
            U2String val = opt->second.currentValue;
            U2String::size_type pos = val.find("Landscape");

            if (pos != U2String::npos)
            {
                // Convert x,y since glViewport is expecting the x,y to be lower-left of the portrait orientation
                if (val.find("Left") != U2String::npos)
                {
                    GLsizei temp = x;
                    x = y;
                    y = target->getWidth() - w - temp;
                }
                else
                {
                    GLsizei temp = x;
                    x = target->getHeight() - h - y;
                    y = temp;
                }

                GLsizei temp = h;
                h = w;
                w = temp;
            }
        }
#endif
        glViewport(x, y, w, h);
        GL_CHECK_ERROR;

        // Configure the viewport clipping
        glScissor(x, y, w, h);
        GL_CHECK_ERROR;

        vp->_clearUpdatedFlag();
    }
}

void GLESRenderSystem::_setRenderTarget(U2RenderTarget *target)
{
    // Unbind frame buffer object
    if(mActiveRenderTarget)
        mRTTManager->unbind(mActiveRenderTarget);

    mActiveRenderTarget = target;
    if (target)
    {
        // Switch context if different from current one
        U2GLESContext *newContext = 0;
        target->getCustomAttribute("GLCONTEXT", &newContext);
        if (newContext && mCurrentContext != newContext)
        {
            _switchContext(newContext);
        }

        // Check the FBO's depth buffer status
        U2GLESDepthBuffer *depthBuffer = static_cast<U2GLESDepthBuffer*>(target->getDepthBuffer());

        if( target->getDepthBufferPool() != U2DepthBuffer::POOL_NO_DEPTH &&
            (!depthBuffer || depthBuffer->getGLContext() != mCurrentContext ) )
        {
            // Depth is automatically managed and there is no depth buffer attached to this RT
            // or the Current context doesn't match the one this Depth buffer was created with
            setDepthBufferFor( target );
        }

        // Bind frame buffer object
        mRTTManager->bind(target);
    }
}

//---------------------------------------------------------------------
U2DepthBuffer* GLESRenderSystem::_createDepthBufferFor( U2RenderTarget *renderTarget )
{
    U2GLESDepthBuffer *retVal = 0;

    // Only FBO & pbuffer support different depth buffers, so everything
    // else creates dummy (empty) containers
    // retVal = mRTTManager->_createDepthBufferFor( renderTarget );
    GLESFrameBufferObject *fbo = 0;
    renderTarget->getCustomAttribute("FBO", &fbo);

    if( fbo )
    {
        // Presence of an FBO means the manager is an FBO Manager, that's why it's safe to downcast
        // Find best depth & stencil format suited for the RT's format
        GLuint depthFormat, stencilFormat;
        static_cast<GLESFBOManager*>(mRTTManager)->getBestDepthStencil( fbo->getFormat(),
            &depthFormat, &stencilFormat );

        GLESRenderBuffer *depthBuffer = U2_NEW GLESRenderBuffer( depthFormat, fbo->getWidth(),
            fbo->getHeight(), fbo->getFSAA() );

        GLESRenderBuffer *stencilBuffer = depthBuffer;
        if( 
            // not supported on AMD emulation for now...
#ifdef GL_DEPTH24_STENCIL8_OES
            depthFormat != GL_DEPTH24_STENCIL8_OES && 
#endif
            stencilBuffer )
        {
            stencilBuffer = U2_NEW GLESRenderBuffer( stencilFormat, fbo->getWidth(),
                fbo->getHeight(), fbo->getFSAA() );
        }

        //No "custom-quality" multisample for now in GL
        retVal = U2_NEW U2GLESDepthBuffer( 0, this, mCurrentContext, depthBuffer, stencilBuffer,
            fbo->getWidth(), fbo->getHeight(), fbo->getFSAA(), 0, false );
    }

    return retVal;
}

//---------------------------------------------------------------------
void GLESRenderSystem::_getDepthStencilFormatFor( GLenum internalColourFormat, GLenum *depthFormat,
                                                 GLenum *stencilFormat )
{
    mRTTManager->getBestDepthStencil( internalColourFormat, depthFormat, stencilFormat );
}

U2RenderWindow* GLESRenderSystem::_initialise(bool autoCreateWindow,
                                            const U2String& windowTitle)
{
    mGLSupport->start();

    U2RenderWindow *autoWindow = mGLSupport->createWindow(autoCreateWindow,
        this, windowTitle);
    U2RenderSystem::_initialise(autoCreateWindow, windowTitle);
    return autoWindow;
}

void GLESRenderSystem::_render(const U2RenderOperation& op)
{
    GL_CHECK_ERROR;
    // Call super class
    U2RenderSystem::_render(op);

    void* pBufferData = 0;
    bool multitexturing = (getCapabilities()->getNumTextureUnits() > 1);

    const U2VertexDeclaration::VertexElementList& decl =
        op.vertexData->vertexDeclaration->getElements();

    U2VertexDeclaration::VertexElementList::const_iterator elem, elemEnd;

    elemEnd = decl.end();
    std::vector<GLuint> attribsBound;

    for (elem = decl.begin(); elem != elemEnd; ++elem)
    {
        if (!op.vertexData->vertexBufferBinding->isBufferBound(elem->getSource()))
            continue; // skip unbound elements

        U2HardwareVertexBufferSharedPtr vertexBuffer =
            op.vertexData->vertexBufferBinding->getBuffer(elem->getSource());
        if (mCurrentCapabilities->hasCapability(RSC_VBO))
        {
            glBindBuffer(GL_ARRAY_BUFFER,
                static_cast<const GLESHardwareVertexBuffer*>(vertexBuffer.get())->getGLBufferId());
            GL_CHECK_ERROR;
            pBufferData = VBO_BUFFER_OFFSET(elem->getOffset());
        }
        else
        {
            pBufferData = static_cast<const U2GLESDefaultHardwareVertexBuffer*>(vertexBuffer.get())->getDataPtr(elem->getOffset());
        }

        if (op.vertexData->vertexStart)
        {
            pBufferData = static_cast<char*>(pBufferData) + op.vertexData->vertexStart * vertexBuffer->getVertexSize();
        }

        unsigned int i = 0;
        VertexElementSemantic sem = elem->getSemantic();

        {
            GL_CHECK_ERROR;
            // fixed-function & builtin attribute support
            switch (sem)
            {
            case VES_POSITION:
                glVertexPointer(U2VertexElement::getTypeCount(elem->getType()),
                    U2GLESHardwareBufferManager::getGLType(elem->getType()),
                    static_cast<GLsizei>(vertexBuffer->getVertexSize()),
                    pBufferData);
                GL_CHECK_ERROR;
                glEnableClientState(GL_VERTEX_ARRAY);
                GL_CHECK_ERROR;
                break;
            case VES_NORMAL:
                glNormalPointer(U2GLESHardwareBufferManager::getGLType(elem->getType()),
                    static_cast<GLsizei>(vertexBuffer->getVertexSize()),
                    pBufferData);
                GL_CHECK_ERROR;
                glEnableClientState(GL_NORMAL_ARRAY);
                GL_CHECK_ERROR;
                break;
            case VES_DIFFUSE:
                glColorPointer(4, U2GLESHardwareBufferManager::getGLType(elem->getType()),
                    static_cast<GLsizei>(vertexBuffer->getVertexSize()),
                    pBufferData);
                GL_CHECK_ERROR;
                glEnableClientState(GL_COLOR_ARRAY);
                GL_CHECK_ERROR;
                break;
            case VES_SPECULAR:
                break;
            case VES_TEXTURE_COORDINATES:
                {
                    // fixed function matching to units based on tex_coord_set
                    for (i = 0; i < mDisabledTexUnitsFrom; i++)
                    {
                        // Only set this texture unit's texcoord pointer if it
                        // is supposed to be using this element's index
                        if (mTextureCoordIndex[i] == elem->getIndex() && i < mFixedFunctionTextureUnits)
                        {
                            GL_CHECK_ERROR;
                            if (multitexturing)
                                glClientActiveTexture(GL_TEXTURE0 + i);
                            GL_CHECK_ERROR;
                            glTexCoordPointer(U2VertexElement::getTypeCount(elem->getType()),
                                U2GLESHardwareBufferManager::getGLType(elem->getType()),
                                static_cast<GLsizei>(vertexBuffer->getVertexSize()),
                                pBufferData);
                            GL_CHECK_ERROR;
                            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                            GL_CHECK_ERROR;
                        }
                    }
                }
                break;
            default:
                break;
            };
        }
    }

    if (multitexturing)
        glClientActiveTexture(GL_TEXTURE0);
    GL_CHECK_ERROR;

    // Find the correct type to render
    GLint primType;
    switch (op.operationType)
    {
    case U2RenderOperation::OT_POINT_LIST:
        primType = GL_POINTS;
        break;
    case U2RenderOperation::OT_LINE_LIST:
        primType = GL_LINES;
        break;
    case U2RenderOperation::OT_LINE_STRIP:
        primType = GL_LINE_STRIP;
        break;
    default:
    case U2RenderOperation::OT_TRIANGLE_LIST:
        primType = GL_TRIANGLES;
        break;
    case U2RenderOperation::OT_TRIANGLE_STRIP:
        primType = GL_TRIANGLE_STRIP;
        break;
    case U2RenderOperation::OT_TRIANGLE_FAN:
        primType = GL_TRIANGLE_FAN;
        break;
    }

    if (op.useIndexes)
    {
        if(mCurrentCapabilities->hasCapability(RSC_VBO))
        {
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
                static_cast<GLESHardwareIndexBuffer*>(op.indexData->indexBuffer.get())->getGLBufferId());

            GL_CHECK_ERROR;
            pBufferData = VBO_BUFFER_OFFSET(op.indexData->indexStart *
                op.indexData->indexBuffer->getIndexSize());
        }
        else
        {
            pBufferData = static_cast<U2GLESDefaultHardwareIndexBuffer*>(
                op.indexData->indexBuffer.get())->getDataPtr(
                op.indexData->indexStart * op.indexData->indexBuffer->getIndexSize());
        }

        GLenum indexType = (op.indexData->indexBuffer->getType() == U2HardwareIndexBuffer::IT_16BIT) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;

        /**********************@@@@@@@@@@@@@@@@@@@@@
        do
        {
            // Update derived depth bias
            if (mDerivedDepthBias && mCurrentPassIterationNum > 0)
            {
                _setDepthBias(mDerivedDepthBiasBase +
                    mDerivedDepthBiasMultiplier * mCurrentPassIterationNum,
                    mDerivedDepthBiasSlopeScale);
            }
            GL_CHECK_ERROR;
            glDrawElements((_getPolygonMode() == GL_FILL) ? primType : _getPolygonMode(), op.indexData->indexCount, indexType, pBufferData);
            GL_CHECK_ERROR;
        } while (updatePassIterationRenderState());
        */
    }
    else
    {
        /**********************@@@@@@@@@@@@@@@@@@@@@
        do
        {
            // Update derived depth bias
            if (mDerivedDepthBias && mCurrentPassIterationNum > 0)
            {
                _setDepthBias(mDerivedDepthBiasBase +
                    mDerivedDepthBiasMultiplier * mCurrentPassIterationNum,
                    mDerivedDepthBiasSlopeScale);
            }
            glDrawArrays((_getPolygonMode() == GL_FILL) ? primType : _getPolygonMode(), 0, op.vertexData->vertexCount);
            GL_CHECK_ERROR;
        } while (updatePassIterationRenderState());
        */
        glDrawArrays(primType, 0, op.vertexData->vertexCount);
        GL_CHECK_ERROR;
    }

    glDisableClientState(GL_VERTEX_ARRAY);
    GL_CHECK_ERROR;

    // Only valid up to GL_MAX_TEXTURE_UNITS, which is recorded in mFixedFunctionTextureUnits
    if (multitexturing)
    {
        for (int i = 0; i < mFixedFunctionTextureUnits; i++)
        {
            glClientActiveTexture(GL_TEXTURE0 + i);
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        }
        glClientActiveTexture(GL_TEXTURE0);
    }
    else
    {
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    GL_CHECK_ERROR;
    glDisableClientState(GL_NORMAL_ARRAY);
    GL_CHECK_ERROR;
    glDisableClientState(GL_COLOR_ARRAY);
    GL_CHECK_ERROR;

    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    GL_CHECK_ERROR;
}

void GLESRenderSystem::_beginFrame(void)
{
    if (!mActiveViewport)
        U2_EXCEPT(U2Exception::ERR_INVALID_STATE,
        "Cannot begin frame - no viewport selected.",
        "GLESRenderSystem::_beginFrame");

    if(mCurrentCapabilities->hasCapability(RSC_SCISSOR_TEST)) {
        glEnable(GL_SCISSOR_TEST);
        GL_CHECK_ERROR;
    }
}

void GLESRenderSystem::_endFrame(void)
{
    // Deactivate the viewport clipping.
    if(mCurrentCapabilities->hasCapability(RSC_SCISSOR_TEST)) {
        glDisable(GL_SCISSOR_TEST);
        GL_CHECK_ERROR;
    }
}

void GLESRenderSystem::_setWorldMatrix(const U2Matrix4 &m)
{
    GLfloat mat[16];
    mWorldMatrix = m;
    makeGLMatrix(mat, mViewMatrix * mWorldMatrix);
    glMatrixMode(GL_MODELVIEW);
    GL_CHECK_ERROR;
    glLoadMatrixf(mat);
    GL_CHECK_ERROR;
}

void GLESRenderSystem::_setViewMatrix(const U2Matrix4 &m)
{
    mViewMatrix = m;

    GLfloat mat[16];
    makeGLMatrix(mat, mViewMatrix * mWorldMatrix);
    glMatrixMode(GL_MODELVIEW);
    GL_CHECK_ERROR;
    glLoadMatrixf(mat);
    GL_CHECK_ERROR;

    // Also mark clip planes dirty
    if (!mClipPlanes.empty())
    {
        mClipPlanesDirty = true;
    }
}

void GLESRenderSystem::_setProjectionMatrix(const U2Matrix4 &m)
{
    GLfloat mat[16];
    makeGLMatrix(mat, m);
    if (mActiveRenderTarget->requiresTextureFlipping())
    {
        // Invert transformed y
        mat[1] = -mat[1];
        mat[5] = -mat[5];
        mat[9] = -mat[9];
        mat[13] = -mat[13];
    }
    glMatrixMode(GL_PROJECTION);
    GL_CHECK_ERROR;
    glLoadMatrixf(mat);
    GL_CHECK_ERROR;

    glMatrixMode(GL_MODELVIEW);
    GL_CHECK_ERROR;

    if (!mClipPlanes.empty())
        mClipPlanesDirty = true;
}

void GLESRenderSystem::makeGLMatrix(GLfloat gl_matrix[16], const U2Matrix4& m)
{
    size_t x = 0;

    for (size_t i = 0; i < 4; i++)
    {
        for (size_t j = 0; j < 4; j++)
        {
            gl_matrix[x] = m[j][i];
            x++;
        }
    }
}

void GLESRenderSystem::setClipPlanesImpl(const U2PlaneList& clipPlanes)
{
    // A note on GL user clipping:
    // When an ARB vertex program is enabled in GL, user clipping is completely
    // disabled. There is no way around this, it's just turned off.
    // When using GLSL, user clipping can work but you have to include a
    // glClipVertex command in your vertex shader.
    // Thus the planes set here may not actually be respected.
    int i = 0;
    int numClipPlanes;
    GLfloat clipPlane[4];

    // Save previous modelview
    glMatrixMode(GL_MODELVIEW);
    GL_CHECK_ERROR;
    glPushMatrix();
    GL_CHECK_ERROR;
    // just load view matrix (identity world)
    GLfloat mat[16];
    makeGLMatrix(mat, mViewMatrix);
    glLoadMatrixf(mat);
    GL_CHECK_ERROR;

    numClipPlanes = clipPlanes.size();

    GLint maxClip;
    glGetIntegerv(GL_MAX_CLIP_PLANES, &maxClip);

    for (i = 0; i < numClipPlanes; ++i)
    {
        GLenum clipPlaneId = static_cast<GLenum>(GL_CLIP_PLANE0 + i);
        const U2Plane& plane = clipPlanes[i];

        if (i >= maxClip)
        {
            U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                "Unable to set clip plane",
                "GLESRenderSystem::setClipPlanes");
        }

        clipPlane[0] = plane.normal.x;
        clipPlane[1] = plane.normal.y;
        clipPlane[2] = plane.normal.z;
        clipPlane[3] = plane.d;

        glClipPlanef(clipPlaneId, clipPlane);
        GL_CHECK_ERROR;
        glEnable(clipPlaneId);
        GL_CHECK_ERROR;
    }

    // disable remaining clip planes
    for ( ; i < maxClip; ++i)
    {
        glDisable(static_cast<GLenum>(GL_CLIP_PLANE0 + i));
        GL_CHECK_ERROR;
    }

    // restore matrices
    glPopMatrix();
    GL_CHECK_ERROR;
}

void GLESRenderSystem::_convertProjectionMatrix(const U2Matrix4& matrix,
                                                U2Matrix4& dest,
                                                bool forGpuProgram)
{
    // no any conversion request for OpenGL
    dest = matrix;
}

void GLESRenderSystem::_makeOrthoMatrix(const Radian& fovy, u2real aspect,
                                        u2real nearPlane, u2real farPlane,
                                        U2Matrix4& dest, bool forGpuProgram)
{
    Radian thetaY(fovy / 2.0f);
    u2real tanThetaY = U2Math::Tan(thetaY);

    // u2real thetaX = thetaY * aspect;
    u2real tanThetaX = tanThetaY * aspect; // Math::Tan(thetaX);
    u2real half_w = tanThetaX * nearPlane;
    u2real half_h = tanThetaY * nearPlane;
    u2real iw = 1.0 / half_w;
    u2real ih = 1.0 / half_h;
    u2real q;
    if (farPlane == 0)
    {
        q = 0;
    }
    else
    {
        q = 2.0 / (farPlane - nearPlane);
    }
    dest = U2Matrix4::ZERO;
    dest[0][0] = iw;
    dest[1][1] = ih;
    dest[2][2] = -q;
    dest[2][3] = -(farPlane + nearPlane) / (farPlane - nearPlane);
    dest[3][3] = 1;
}

void GLESRenderSystem::_setTexture(size_t stage, bool enabled, const U2TexturePtr &texPtr)
{
    GL_CHECK_ERROR;

    // TODO We need control texture types?????
    U2GLESTexturePtr tex = texPtr;

    if (!activateGLTextureUnit(stage))
        return;

    if (enabled)
    {
        if (!tex.isNull())
        {
            // Note used
            tex->touch();

            // Store the number of mipmaps
            mTextureMipmapCount = tex->getNumMipmaps();
        }

        glEnable(GL_TEXTURE_2D);
        GL_CHECK_ERROR;

        if (!tex.isNull())
        {
            glBindTexture(GL_TEXTURE_2D, tex->getGLID());
            GL_CHECK_ERROR;
        }
        else
        {
            glBindTexture(GL_TEXTURE_2D, static_cast<U2GLESTextureManager*>(mTextureManager)->getWarningTextureID());
            GL_CHECK_ERROR;
        }
    }
    else
    {
        glEnable(GL_TEXTURE_2D);
        glDisable(GL_TEXTURE_2D);
        GL_CHECK_ERROR;

        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        GL_CHECK_ERROR;

        // bind zero texture
        glBindTexture(GL_TEXTURE_2D, 0);
        GL_CHECK_ERROR;
    }

    activateGLTextureUnit(0);
}

bool GLESRenderSystem::activateGLTextureUnit(size_t unit)
{
    if (mActiveTextureUnit != unit)
    {
        if (unit < getCapabilities()->getNumTextureUnits())
        {
            glActiveTexture(GL_TEXTURE0 + unit);
            GL_CHECK_ERROR;
            mActiveTextureUnit = unit;
            return true;
        }
        else if (!unit)
        {
            // always ok to use the first unit
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return true;
    }
}

void GLESRenderSystem::clearFrameBuffer(unsigned int buffers,
                                        const U2ColourValue& colour,
                                        u2real depth/*, unsigned short stencil*/)
{
    bool colourMask = !mColourWrite[0] || !mColourWrite[1] ||
        !mColourWrite[2] || !mColourWrite[3];

    GLbitfield flags = 0;
    if (buffers & FBT_COLOUR)
    {
        flags |= GL_COLOR_BUFFER_BIT;
        // Enable buffer for writing if it isn't
        if (colourMask)
        {
            glColorMask(true, true, true, true);
            GL_CHECK_ERROR;
        }
        glClearColor(colour.r, colour.g, colour.b, colour.a);
        GL_CHECK_ERROR;
    }
    if (buffers & FBT_DEPTH)
    {
        flags |= GL_DEPTH_BUFFER_BIT;
        // Enable buffer for writing if it isn't
        if (!mDepthWrite)
        {
            glDepthMask(GL_TRUE);
            GL_CHECK_ERROR;
        }
        glClearDepthf(depth);
        GL_CHECK_ERROR;
    }
    /**********************@@@@@@@@@@@@@@@@@@@@@
    if (buffers & FBT_STENCIL)
    {
        flags |= GL_STENCIL_BUFFER_BIT;
        // Enable buffer for writing if it isn't
        glStencilMask(0xFFFFFFFF);
        GL_CHECK_ERROR;
        glClearStencil(stencil);
        GL_CHECK_ERROR;
    }
    */

    // Should be enable scissor test due the clear region is
    // relied on scissor box bounds.
    GLboolean scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST);
    GL_CHECK_ERROR;
    if (!scissorTestEnabled)
    {
        glEnable(GL_SCISSOR_TEST);
        GL_CHECK_ERROR;
    }

    // Sets the scissor box as same as viewport
    GLint viewport[4], scissor[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    GL_CHECK_ERROR;
    glGetIntegerv(GL_SCISSOR_BOX, scissor);
    GL_CHECK_ERROR;
    bool scissorBoxDifference =
        viewport[0] != scissor[0] || viewport[1] != scissor[1] ||
        viewport[2] != scissor[2] || viewport[3] != scissor[3];
    if (scissorBoxDifference)
    {
        glScissor(viewport[0], viewport[1], viewport[2], viewport[3]);
        GL_CHECK_ERROR;
    }

    _setDiscardBuffers(buffers);

    // Clear buffers
    glClear(flags);
    GL_CHECK_ERROR;

    // Restore scissor box
    if (scissorBoxDifference)
    {
        glScissor(scissor[0], scissor[1], scissor[2], scissor[3]);
        GL_CHECK_ERROR;
    }

    // Restore scissor test
    if (!scissorTestEnabled)
    {
        glDisable(GL_SCISSOR_TEST);
        GL_CHECK_ERROR;
    }

    // Reset buffer write state
    if (!mDepthWrite && (buffers & FBT_DEPTH))
    {
        glDepthMask(GL_FALSE);
        GL_CHECK_ERROR;
    }

    if (colourMask && (buffers & FBT_COLOUR))
    {
        glColorMask(mColourWrite[0], mColourWrite[1], mColourWrite[2], mColourWrite[3]);
        GL_CHECK_ERROR;
    }

    if (buffers & FBT_STENCIL)
    {
        glStencilMask(mStencilMask);
        GL_CHECK_ERROR;
    }
}

void GLESRenderSystem::_setPolygonMode(PolygonMode level)
{
    switch(level)
    {
    case PM_POINTS:
        mPolygonMode = GL_POINTS;
        break;
    case PM_WIREFRAME:
        mPolygonMode = GL_LINE_STRIP;
        break;
    default:
    case PM_SOLID:
        mPolygonMode = GL_FILL;
        break;
    }
}

void GLESRenderSystem::_setTextureCoordSet(size_t stage, size_t index)
{
    mTextureCoordIndex[stage] = index;
}

void GLESRenderSystem::_setTextureUnitFiltering(size_t unit, FilterType ftype, FilterOptions fo)
{
    if (!activateGLTextureUnit(unit))
        return;

    switch (ftype)
    {
    case FT_MIN:
        if(mTextureMipmapCount == 0)
        {
            mMinFilter = FO_NONE;
        }
        else
        {
            mMinFilter = fo;
        }

        // Combine with existing mip filter
        glTexParameteri(GL_TEXTURE_2D,
            GL_TEXTURE_MIN_FILTER,
            getCombinedMinMipFilter());
        GL_CHECK_ERROR;
        break;

    case FT_MAG:
        switch (fo)
        {
        case FO_ANISOTROPIC: // GL treats linear and aniso the same
        case FO_LINEAR:
            glTexParameteri(GL_TEXTURE_2D,
                GL_TEXTURE_MAG_FILTER,
                GL_LINEAR);
            GL_CHECK_ERROR;
            break;
        case FO_POINT:
        case FO_NONE:
            glTexParameteri(GL_TEXTURE_2D,
                GL_TEXTURE_MAG_FILTER,
                GL_NEAREST);
            GL_CHECK_ERROR;
            break;
        }
        break;
    case FT_MIP:
        if(mTextureMipmapCount == 0)
        {
            mMipFilter = FO_NONE;
        }
        else
        {
            mMipFilter = fo;
        }

        // Combine with existing min filter
        glTexParameteri(GL_TEXTURE_2D,
            GL_TEXTURE_MIN_FILTER,
            getCombinedMinMipFilter());
        GL_CHECK_ERROR;
        break;
    }

    activateGLTextureUnit(0);
}

GLuint GLESRenderSystem::getCombinedMinMipFilter(void) const
{
    switch(mMinFilter)
    {
    case FO_ANISOTROPIC:
    case FO_LINEAR:
        switch (mMipFilter)
        {
        case FO_ANISOTROPIC:
        case FO_LINEAR:
            // linear min, linear mip
            return GL_LINEAR_MIPMAP_LINEAR;
        case FO_POINT:
            // linear min, point mip
            return GL_LINEAR_MIPMAP_NEAREST;
        case FO_NONE:
            // linear min, no mip
            return GL_LINEAR;
        }
        break;
    case FO_POINT:
    case FO_NONE:
        switch (mMipFilter)
        {
        case FO_ANISOTROPIC:
        case FO_LINEAR:
            // nearest min, linear mip
            return GL_NEAREST_MIPMAP_LINEAR;
        case FO_POINT:
            // nearest min, point mip
            return GL_NEAREST_MIPMAP_NEAREST;
        case FO_NONE:
            // nearest min, no mip
            return GL_NEAREST;
        }
        break;
    }

    // should never get here
    return 0;
}
/**********************@@@@@@@@@@@@@@@@@@@@@
void GLESRenderSystem::_setTextureLayerAnisotropy(size_t unit, unsigned int maxAnisotropy)
{
    if (!mCurrentCapabilities->hasCapability(RSC_ANISOTROPY))
        return;

    if (!activateGLTextureUnit(unit))
        return;

    GLfloat largest_supported_anisotropy = 0;
    glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_supported_anisotropy);
    if (maxAnisotropy > largest_supported_anisotropy)
        maxAnisotropy = largest_supported_anisotropy ? 
        static_cast<u2uint>(largest_supported_anisotropy) : 1;
    if (_getCurrentAnisotropy(unit) != maxAnisotropy)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);

    activateGLTextureUnit(0);
}
*/
GLfloat GLESRenderSystem::_getCurrentAnisotropy(size_t unit)
{
    GLfloat curAniso = 0;
    glGetTexParameterfv(GL_TEXTURE_2D, 
        GL_TEXTURE_MAX_ANISOTROPY_EXT, &curAniso);
    return curAniso ? curAniso : 1;
}

void GLESRenderSystem::_setTextureMipmapBias(size_t unit, float bias)
{
    if (mCurrentCapabilities->hasCapability(RSC_MIPMAP_LOD_BIAS))
    {
#if GL_EXT_texture_lod_bias	// This extension only seems to be supported on iPhone OS, block it out to fix Linux build
        if (activateGLTextureUnit(unit))
        {
            glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, bias);
            activateGLTextureUnit(0);
        }
#endif
    }
}

void GLESRenderSystem::_setTextureBlendMode(size_t stage, const LayerBlendModeEx& bm)
{
    if (stage >= mFixedFunctionTextureUnits)
    {
        // Can't do this
        return;
    }

    // Check to see if blending is supported
    if (!mCurrentCapabilities->hasCapability(RSC_BLENDING))
        return;

    GLenum src1op, src2op, cmd;
    GLfloat cv1[4], cv2[4];

    if (bm.blendType == LBT_COLOUR)
    {
        cv1[0] = bm.colourArg1.r;
        cv1[1] = bm.colourArg1.g;
        cv1[2] = bm.colourArg1.b;
        cv1[3] = bm.colourArg1.a;
        mManualBlendColours[stage][0] = bm.colourArg1;

        cv2[0] = bm.colourArg2.r;
        cv2[1] = bm.colourArg2.g;
        cv2[2] = bm.colourArg2.b;
        cv2[3] = bm.colourArg2.a;
        mManualBlendColours[stage][1] = bm.colourArg2;
    }

    if (bm.blendType == LBT_ALPHA)
    {
        cv1[0] = mManualBlendColours[stage][0].r;
        cv1[1] = mManualBlendColours[stage][0].g;
        cv1[2] = mManualBlendColours[stage][0].b;
        cv1[3] = bm.alphaArg1;

        cv2[0] = mManualBlendColours[stage][1].r;
        cv2[1] = mManualBlendColours[stage][1].g;
        cv2[2] = mManualBlendColours[stage][1].b;
        cv2[3] = bm.alphaArg2;
    }

    switch (bm.source1)
    {
    case LBS_CURRENT:
        src1op = GL_PREVIOUS;
        break;
    case LBS_TEXTURE:
        src1op = GL_TEXTURE;
        break;
    case LBS_MANUAL:
        src1op = GL_CONSTANT;
        break;
    case LBS_DIFFUSE:
        src1op = GL_PRIMARY_COLOR;
        break;
    case LBS_SPECULAR:
        src1op = GL_PRIMARY_COLOR;
        break;
    default:
        src1op = 0;
    }

    switch (bm.source2)
    {
    case LBS_CURRENT:
        src2op = GL_PREVIOUS;
        break;
    case LBS_TEXTURE:
        src2op = GL_TEXTURE;
        break;
    case LBS_MANUAL:
        src2op = GL_CONSTANT;
        break;
    case LBS_DIFFUSE:
        src2op = GL_PRIMARY_COLOR;
        break;
    case LBS_SPECULAR:
        src2op = GL_PRIMARY_COLOR;
        break;
    default:
        src2op = 0;
    }

    switch (bm.operation)
    {
    case LBX_SOURCE1:
        cmd = GL_REPLACE;
        break;
    case LBX_SOURCE2:
        cmd = GL_REPLACE;
        break;
    case LBX_MODULATE:
        cmd = GL_MODULATE;
        break;
    case LBX_MODULATE_X2:
        cmd = GL_MODULATE;
        break;
    case LBX_MODULATE_X4:
        cmd = GL_MODULATE;
        break;
    case LBX_ADD:
        cmd = GL_ADD;
        break;
    case LBX_ADD_SIGNED:
        cmd = GL_ADD_SIGNED;
        break;
    case LBX_ADD_SMOOTH:
        cmd = GL_INTERPOLATE;
        break;
    case LBX_SUBTRACT:
        cmd = GL_SUBTRACT;
        break;
    case LBX_BLEND_DIFFUSE_COLOUR:
        cmd = GL_INTERPOLATE;
        break; 
    case LBX_BLEND_DIFFUSE_ALPHA:
        cmd = GL_INTERPOLATE;
        break;
    case LBX_BLEND_TEXTURE_ALPHA:
        cmd = GL_INTERPOLATE;
        break;
    case LBX_BLEND_CURRENT_ALPHA:
        cmd = GL_INTERPOLATE;
        break;
    case LBX_BLEND_MANUAL:
        cmd = GL_INTERPOLATE;
        break;
    case LBX_DOTPRODUCT:
        cmd = mCurrentCapabilities->hasCapability(RSC_DOT3)
            ? GL_DOT3_RGB : GL_MODULATE;
        break;
    default:
        cmd = 0;
    }

    if (!activateGLTextureUnit(stage))
        return;
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
    GL_CHECK_ERROR;

    if (bm.blendType == LBT_COLOUR)
    {
        glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, cmd);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, src1op);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, src2op);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_CONSTANT);
        GL_CHECK_ERROR;
    }
    else
    {
        glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, cmd);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, src1op);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, src2op);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ALPHA, GL_CONSTANT);
        GL_CHECK_ERROR;
    }

    float blendValue[4] = {0, 0, 0, bm.factor};
    switch (bm.operation)
    {
    case LBX_BLEND_DIFFUSE_COLOUR:
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_PRIMARY_COLOR);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ALPHA, GL_PRIMARY_COLOR);
        GL_CHECK_ERROR;
        break;
    case LBX_BLEND_DIFFUSE_ALPHA:
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_PRIMARY_COLOR);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ALPHA, GL_PRIMARY_COLOR);
        GL_CHECK_ERROR;
        break;
    case LBX_BLEND_TEXTURE_ALPHA:
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_TEXTURE);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ALPHA, GL_TEXTURE);
        GL_CHECK_ERROR;
        break;
    case LBX_BLEND_CURRENT_ALPHA:
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_PREVIOUS);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ALPHA, GL_PREVIOUS);
        GL_CHECK_ERROR;
        break;
    case LBX_BLEND_MANUAL:
        glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, blendValue);
        GL_CHECK_ERROR;
        break;
    default:
        break;
    };

    switch (bm.operation)
    {
    case LBX_MODULATE_X2:
        glTexEnvi(GL_TEXTURE_ENV, bm.blendType == LBT_COLOUR ?
GL_RGB_SCALE : GL_ALPHA_SCALE, 2);
        GL_CHECK_ERROR;
        break;
    case LBX_MODULATE_X4:
        glTexEnvi(GL_TEXTURE_ENV, bm.blendType == LBT_COLOUR ?
GL_RGB_SCALE : GL_ALPHA_SCALE, 4);
        GL_CHECK_ERROR;

        break;
    default:
        glTexEnvi(GL_TEXTURE_ENV, bm.blendType == LBT_COLOUR ?
GL_RGB_SCALE : GL_ALPHA_SCALE, 1);
        GL_CHECK_ERROR;
        break;
    }

    if (bm.blendType == LBT_COLOUR)
    {
        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
        GL_CHECK_ERROR;
        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
        GL_CHECK_ERROR;
        if (bm.operation == LBX_BLEND_DIFFUSE_COLOUR)
        {
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
            GL_CHECK_ERROR;
        }
        else
        {
            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA);
            GL_CHECK_ERROR;
        }
    }

    glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
    GL_CHECK_ERROR;
    glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
    GL_CHECK_ERROR;
    glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, GL_SRC_ALPHA);
    GL_CHECK_ERROR;
    if (bm.source1 == LBS_MANUAL)
        glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, cv1);
    if (bm.source2 == LBS_MANUAL)
        glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, cv2);

    activateGLTextureUnit(0);
}

void GLESRenderSystem::_setTextureAddressingMode(size_t stage, const TextureUnitState::UVWAddressingMode& uvw)
{
    if (!activateGLTextureUnit(stage))
        return;
    glTexParameteri(GL_TEXTURE_2D,
        GL_TEXTURE_WRAP_S, getTextureAddressingMode(uvw.u));
    GL_CHECK_ERROR;
    glTexParameteri(GL_TEXTURE_2D,
        GL_TEXTURE_WRAP_T, getTextureAddressingMode(uvw.v));
    GL_CHECK_ERROR;
    activateGLTextureUnit(0);
}

GLint GLESRenderSystem::getTextureAddressingMode(TextureUnitState::TextureAddressingMode tam) const
{
    switch (tam)
    {
    case TextureUnitState::TAM_CLAMP:
    case TextureUnitState::TAM_BORDER:
        return GL_CLAMP_TO_EDGE;
    case TextureUnitState::TAM_MIRROR:
#if GL_OES_texture_mirrored_repeat
        return GL_MIRRORED_REPEAT_OES;
#endif
    case TextureUnitState::TAM_WRAP:
    default:
        return GL_REPEAT;
    }
}

void GLESRenderSystem::_setTextureBorderColour(size_t stage, const U2ColourValue& colour)
{
    // Not supported
}
/**********************@@@@@@@@@@@@@@@@@@@@@
void GLESRenderSystem::_setTextureCoordCalculation(size_t stage,
                                                   TexCoordCalcMethod m,
                                                   const Frustum* frustum)
{
    if (stage >= mFixedFunctionTextureUnits)
    {
        // Can't do this
        return;
    }

    GLfloat M[16];
    Matrix4 projectionBias;

    // Default to no extra auto texture matrix
    mUseAutoTextureMatrix = false;

    if (!activateGLTextureUnit(stage))
        return;
    switch(m)
    {
    case TEXCALC_NONE:
        break;

    case TEXCALC_ENVIRONMENT_MAP:
        mUseAutoTextureMatrix = true;
        memset(mAutoTextureMatrix, 0, sizeof(GLfloat)*16);
        mAutoTextureMatrix[0] = mAutoTextureMatrix[10] = mAutoTextureMatrix[15] = 1.0f;
        mAutoTextureMatrix[5] = -1.0f;
        break;

    case TEXCALC_ENVIRONMENT_MAP_PLANAR:
        // TODO not implemented
        break;

    case TEXCALC_ENVIRONMENT_MAP_REFLECTION:
        // We need an extra texture matrix here
        // This sets the texture matrix to be the inverse of the view matrix
        mUseAutoTextureMatrix = true;
        makeGLMatrix(M, mViewMatrix);

        // Transpose 3x3 in order to invert matrix (rotation)
        // Note that we need to invert the Z _before_ the rotation
        // No idea why we have to invert the Z at all, but reflection is wrong without it
        mAutoTextureMatrix[0] = M[0]; mAutoTextureMatrix[1] = M[4]; mAutoTextureMatrix[2] = -M[8];
        mAutoTextureMatrix[4] = M[1]; mAutoTextureMatrix[5] = M[5]; mAutoTextureMatrix[6] = -M[9];
        mAutoTextureMatrix[8] = M[2]; mAutoTextureMatrix[9] = M[6]; mAutoTextureMatrix[10] = -M[10];
        mAutoTextureMatrix[3] = mAutoTextureMatrix[7] = mAutoTextureMatrix[11] = 0.0f;
        mAutoTextureMatrix[12] = mAutoTextureMatrix[13] = mAutoTextureMatrix[14] = 0.0f;
        mAutoTextureMatrix[15] = 1.0f;
        break;

    case TEXCALC_ENVIRONMENT_MAP_NORMAL:
        break;

    case TEXCALC_PROJECTIVE_TEXTURE:
        mUseAutoTextureMatrix = true;

        // Set scale and translation matrix for projective textures
        projectionBias = Matrix4::CLIPSPACE2DTOIMAGESPACE;

        projectionBias = projectionBias * frustum->getProjectionMatrix();
        projectionBias = projectionBias * frustum->getViewMatrix();
        projectionBias = projectionBias * mWorldMatrix;

        makeGLMatrix(mAutoTextureMatrix, projectionBias);
        break;

    default:
        break;
    }

    activateGLTextureUnit(0);
}
*/
void GLESRenderSystem::_setTextureMatrix(size_t stage, const U2Matrix4& xform)
{
    if (stage >= mFixedFunctionTextureUnits)
    {
        // Can't do this
        return;
    }

    if (!activateGLTextureUnit(stage))
        return;

    GLfloat mat[16];
    makeGLMatrix(mat, xform);

    glMatrixMode(GL_TEXTURE);
    GL_CHECK_ERROR;

    // Load this matrix in
    glLoadMatrixf(mat);
    GL_CHECK_ERROR;

    /**********************@@@@@@@@@@@@@@@@@@@@@
    if (mUseAutoTextureMatrix)
    {
        // Concat auto matrix
        glMultMatrixf(mAutoTextureMatrix);
    }
    */

    glMatrixMode(GL_MODELVIEW);
    GL_CHECK_ERROR;
    activateGLTextureUnit(0);
}